package com.icehelper.hades.base.util.httpfluent.config;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import com.icehelper.hades.base.util.httpfluent.constant.InternalContext;
import lombok.Data;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;

/**
 * HttpClientConfig http client配置
 */
@Import({HttpClientConfig.class})
@Data
@Component
public class HttpClientAutoConfig {

  private static HttpClientAutoConfig instance;

  @Resource
  private HttpClientConfig httpClientConfig;

  private RequestConfig requestConfig;

  private CloseableHttpClient closeableHttpClient;

  @PostConstruct
  public void init() {
    if (httpClientConfig == null) {
      httpClientConfig = new HttpClientConfig();
    }
    initRequestConfig();
    initCloseableHttpClient();
    setInstance(this);
  }

  private PoolingHttpClientConnectionManager getPoolManager() {
    PoolingHttpClientConnectionManager pm = new PoolingHttpClientConnectionManager(httpClientConfig.getConnTimeToLive(), TimeUnit.MILLISECONDS);
    pm.setMaxTotal(httpClientConfig.getMaxTotalConnections());
    pm.setDefaultMaxPerRoute(httpClientConfig.getMaxConnectionPerRoute());
    pm.closeIdleConnections(httpClientConfig.getIdle(), TimeUnit.MILLISECONDS);
    pm.setValidateAfterInactivity(httpClientConfig.getPeriodValidateInactivity());
    return pm;
  }

  private void initRequestConfig() {
    RequestConfig.Builder builder = RequestConfig.custom()
        .setMaxRedirects(httpClientConfig.getMaxRedirects())
        .setConnectionRequestTimeout(httpClientConfig.getConnectionRequestTimeout())
        .setConnectTimeout(httpClientConfig.getConnectTimeout())
        .setSocketTimeout(httpClientConfig.getSocketTimeout())
        .setContentCompressionEnabled(httpClientConfig.isContentCompressionEnabled());
    requestConfig = builder.build();
  }

  private void initCloseableHttpClient() {
    ConnectionKeepAliveStrategy connKeepAliveStrategy = (response, context) -> {
      HeaderElementIterator it = new BasicHeaderElementIterator
          (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
      while (it.hasNext()) {
        HeaderElement he = it.nextElement();
        String param = he.getName();
        String value = he.getValue();
        if (value != null && param.equalsIgnoreCase("timeout")) {
          return Long.parseLong(value) * 1000;
        }
      }
      return httpClientConfig.getConnTimeToLive();
    };
    closeableHttpClient =
        HttpClients.custom().setConnectionManager(getPoolManager()).setDefaultRequestConfig(getRequestConfig())
            .setRetryHandler(new HttpRetryHandler(httpClientConfig.getRetry(), httpClientConfig.getRetryAllowException(),
                httpClientConfig.getRetryForbiddenException(), httpClientConfig.getRetryMetricExcludeException(),
                httpClientConfig.getRetryLogExcludeException()))
            .evictIdleConnections(httpClientConfig.getIdle(), TimeUnit.MILLISECONDS)
            .evictExpiredConnections()
            .setConnectionTimeToLive(httpClientConfig.getConnTimeToLive(), TimeUnit.MILLISECONDS)
            .setKeepAliveStrategy(connKeepAliveStrategy)
            .build();
  }

  @Bean
  public CloseableHttpClient closeableHttpClient() {
    return closeableHttpClient;
  }

  private static void setInstance(HttpClientAutoConfig instance) {
    HttpClientAutoConfig.instance = instance;
  }

  public static HttpClientAutoConfig getInstance() {
    if (instance != null) {
      return instance;
    }
    synchronized (HttpClientAutoConfig.class) {
      if (instance != null) {
        return instance;
      }
      HttpClientAutoConfig clientAutoConfig = new HttpClientAutoConfig();
      clientAutoConfig.init();
      return instance;
    }
  }

  /**
   * HttpRetryHandler
   */
  static class HttpRetryHandler implements HttpRequestRetryHandler {

    private final int retry;

    private final Class<?>[] allow;
    private final Class<?>[] forbidden;

    public HttpRetryHandler(int retry, Class<?>[] allow, Class<?>[] forbidden, Class<?>[] metricExclude, Class<?>[] logExclude) {
      this.retry = retry;
      this.allow = allow != null ? allow : new Class<?>[0];
      this.forbidden = forbidden != null ? forbidden : new Class<?>[0];
    }


    public boolean retryRequest(IOException exception, int executionCount, HttpContext httpContext) {
      Object retry = httpContext.getAttribute( InternalContext.RETRY.name());
      if (retry == null) {
        if (this.retry <= 0) {
          return false;
        } else {
          retry = this.retry;
        }
      }

      if (executionCount > (int) retry) {
        return false;
      }
      for (Class<?> clazz : allow) {
        if (clazz.isAssignableFrom(exception.getClass())) {
          return true;
        }
      }
      for (Class<?> clazz : forbidden) {
        if (clazz.isAssignableFrom(exception.getClass())) {
          return false;
        }
      }
      return true;
    }
  }

}
